﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Xml;
using System.IO;
using BReusable;
using System.Text.RegularExpressions;
using System.Diagnostics;
using StarfleetCommander.Models.Persistence;
using System.Text;
using System.Transactions;
using StarfleetCommander.Persistence;
using StarfleetCommander.Persistence.Gen2;

namespace StarfleetCommander.Models
{
    public class GalaxyRecorder
    {
        private const byte locationMatchGalaxyGroup = 1;
        private const byte locationMatchSystemGroup = 2;
        private const byte locationMatchPositionGroup = 3;
        private const byte playerGroups = 5;
        private const string galaxyCapture = "([1-9][0-9]?)";
        private const string systemCapture = "([0-9]+)";
        private const string positionCapture = "([1-9]?[0-5]?)";
        private const string playerCapture = @"(.*)";//@"([a-z]['\-a-z0-9\s]+)";
        private const string statusCapture = @"(\([dvisn!]*\))";
        private const string rankCapture = @"([1-9][0-9\,]+)";
        private const string allianceCapture = @"(.*)?";

        private const byte playerMatchNameGroup = 1;
        private const byte playerMatchStatusGroup = 4;
        private const byte playerMatchRankGroup = 3;
        private const byte playerMatchAllianceGroup = 5;

        byte? _lastPosition;
       readonly  string _logPath;
        readonly Guid _userToken;
        public IEnumerable<ModelColony> Colonies { get { return _colonies; } }
        List<ModelColony> _colonies = new List<ModelColony>();
        Dictionary<string, Alliance> _alliances = new Dictionary<string, Alliance>();
        Dictionary<string, Player> _players = new Dictionary<string, Player>();
        

        public GalaxyRecorder(string logPath,Guid userToken, string data)
        {
            _logPath = logPath;
            _userToken = userToken;

            foreach (var text in data.Split("\r\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries))
            {
                ParseColony(text.Trim());
            }
            AppendXml(data.GetType().FullName, "for", true);

        }

        public GalaxyRecorder(string logPath, Guid userGuid, IEnumerable<string> data)
        {
            _logPath = logPath;
            _userToken = userGuid;

            foreach (var item in data)
            {
                ParseColony(item.Trim());
            }
            AppendXml(data.GetType().FullName, "for", true);
        }

        /// <summary>
        /// Public for testing
        /// </summary>
        /// <param name="trimText"></param>
        /// <param name="logger"></param>
        /// <param name="lastPosition"></param>
        /// <returns></returns>
        public static ModelColony ParseLine(string trimText,Func<string,string,bool,string> logger,ref byte? lastPosition)
        {
           
            //todo: strip out extra status thing
            //format: "60:202:15,Len W Vernamonti (d!) #16,197,(d!),ACCOUSTI" 
            logger(trimText, "colony", true);

            var startIndex = 0;
            var locationMatch = Regex.Match(trimText, GetLocationPattern());
            logger(locationMatch.Captures.Count.ToString(), "locationCaptures", false);
            logger(locationMatch.Groups.Count.ToString(), "locationGroups", true);
            var galaxy = byte.Parse(logger(locationMatch.Groups[locationMatchGalaxyGroup].Value, "galaxyCapture", false).Trim());
            var system = short.Parse(logger(locationMatch.Groups[locationMatchSystemGroup].Value, "systemCapture", false).Trim());
            
            var positionCapture = locationMatch.Groups[locationMatchPositionGroup].Value.Trim();
            logger(positionCapture, "positionCapture", true);
            byte position;
            bool isMoon = false;
            if (positionCapture.Length > 0)
            {
                position = byte.Parse(positionCapture);
                lastPosition = position;
            }
            else
            {
                isMoon = true;
                position = lastPosition.Value;
            }
            logger(position.ToString(), "position", true);
            if(trimText.Contains("#")==false)
                return new ModelColony(galaxy, system, position, isMoon, null, null, 0, null);
            startIndex += locationMatch.Index + locationMatch.Length + 1; //1 for comma

            var playerMatch = Regex.Match(trimText.Substring(startIndex).Trim(),
                GetPlayerPattern(), RegexOptions.IgnoreCase);
            logger(playerMatch.Groups.Count.ToString(), "playerMatchGroups", true);
            //if (playerMatch.Success == false || playerMatch.Groups.Count != playerGroups)
            //{
               
                
            //}

            var name = logger(playerMatch.Groups[playerMatchNameGroup].Value.Trim(), "playerMatchNameCapture", false);
            var status = logger(playerMatch.Groups[playerMatchStatusGroup].Value.Trim().Replace("(", string.Empty).Replace(")", string.Empty)
                , "playerMatchStatusGroup", false);
            var rank = int.Parse(logger(playerMatch.Groups[playerMatchRankGroup].Value.Trim().Replace(",", string.Empty), "playerMatchRankGroup", false));
            // logger("rank parsed", "parseSuccess", false);
            var alliance = logger(playerMatch.Groups[playerMatchAllianceGroup].Value.Trim(), "playerMatchAllianceGroup", true);
            if (status.Length > 0)
                name = name.Substring(0, name.LastIndexOf('(')).Trim();
            return new ModelColony(galaxy, system, position, isMoon, name, status, rank, alliance);
        }
        private void RecordException(Exception exception)
        {
            AppendXml(exception.GetType().FullName, "exceptionType", false);
            AppendXml(exception.Message, "exceptionMessage", true);

           // using (var dc = new StarfleetCommander.Persistence.Gen3. LqSfc2DataContext())
         //   {
            //TODO: record exceptions somewhere?
            //}
        }

        public void Save()
        {
            AppendXml("saving", "Save called", false);

            AppendXml("token converted", "guid", false);
           

                
                using (var dc = new LqSfcDataContext())
                {
                    var sb = new StringBuilder();
                    dc.Log = new StringWriter(sb);
                    AppendXml("DataContext created", "dataContext", true);

                    foreach (var modelColony in _colonies)
                    {
                        Alliance alliance;
                        Player player;
                        GameSystem gameSystem;

                        try
                        {
                            alliance = ProcessAlliance(modelColony.Alliance, dc);
                            AppendXml(alliance != null ? alliance.Shortname : string.Empty, "allianceProcessed", false);
                            
                            gameSystem = ProcessGameSystem(modelColony.Galaxy, modelColony.System, dc);
                            AppendXml(GetSystemPrettyString(gameSystem.Galaxy,gameSystem.System), "systemProcessed", false);
                        }
                        catch (Exception exception)
                        {
                            RecordException(exception);
                            throw;
                        }
                        
                        
                        using (var transaction = new System.Transactions.TransactionScope())
                        {
                            player = ProcessPlayer(modelColony.PlayerName, modelColony.Status, modelColony.Rank, alliance, dc);
                            AppendXml(player != null ? player.Name : string.Empty, "playerProcessed", true);
                            var permission = ProcessPrivacy(gameSystem, _userToken, dc);
                            var colony = ProcessColony(gameSystem, modelColony.Position, modelColony.IsMoon, player, dc);
                            AppendXml(colony != null ? String.Format("{0}:{1}:{2},{3}",
                                gameSystem.Galaxy, gameSystem.System, colony.Position, colony.Moon) : string.Empty, "colonyProcessed", true);

                            AppendXml(_colonies.Count.ToString(), "db saving", true);
                            try
                            {
                                AppendXml(sb.ToString(), "beforesubmit", true);
                                dc.SubmitChanges();
                                AppendXml(_colonies.Count.ToString(), "db saved", true);
                                transaction.Complete();
                            }
                            catch (Exception exception)
                            {
                                RecordException(exception);
                                //AppendXml(exception.StackTrace, "exceptionStackTrace", true);
                                throw;
                            }
                        }
                    }

                }
            
        }

        private SystemPrivacy ProcessPrivacy(GameSystem gameSystem, Guid userToken, LqSfcDataContext dc)
        {

            var userGuid = dc.UserAlliances.Single(s => s.Token==userToken).UserGuid;
            var systemPrivacy=dc.SystemPrivacies.SingleOrDefault(sp => sp.SystemId == gameSystem.Id &
                    sp.UserGuid == userGuid);

            if (systemPrivacy == null)
            {
                systemPrivacy = new SystemPrivacy() { GameSystem = gameSystem,
                                                      UserGuid = userGuid};
                AppendXml(userToken.ToString() + "," + GetSystemPrettyString(gameSystem.Galaxy, gameSystem.System), "newSystemPrivacyRecord", true);
                dc.SystemPrivacies.InsertOnSubmit(systemPrivacy);
            }
            systemPrivacy.Date = DateTime.UtcNow;
            return systemPrivacy;
        }

        private GameSystem ProcessGameSystem(byte galaxy, int system, LqSfcDataContext dc)
        {
           
            var gameSystem = dc.GameSystems.SingleOrDefault(gs => gs.Galaxy == galaxy & gs.System == system);
            if (gameSystem != null)
                return gameSystem;
            gameSystem = new GameSystem() { Galaxy = galaxy, System = system, Updated = DateTime.UtcNow };
            dc.GameSystems.InsertOnSubmit(gameSystem);
            AppendXml(GetSystemPrettyString(galaxy, system), "newSystem", false);
            dc.SubmitChanges();
            AppendXml(GetSystemPrettyString(galaxy, system), "systemInserted", true);
            gameSystem = dc.GameSystems.SingleOrDefault(gs => gs.Galaxy == galaxy & gs.System == system);
            AppendXml(GetSystemPrettyString(galaxy, system), "systemRefected", true);
            return gameSystem;
        }


        private Colony ProcessColony(GameSystem gameSystem, byte position, bool isMoon, Player player, LqSfcDataContext dc)
        {
            var colony = dc.Colonies.SingleOrDefault(c => c.SystemId==gameSystem.Id & c.Position == position & c.Moon == isMoon);
            if (colony != null)
            {//update colony
                colony.Player = player;
                colony.Updated = DateTime.UtcNow;
                return colony;
            }
            colony= new Colony() { Added = DateTime.UtcNow, GameSystem=gameSystem, Moon = isMoon, Player = player, Position = position, Updated = DateTime.UtcNow };
            dc.Colonies.InsertOnSubmit(colony);
            return colony;
        }

        private Player ProcessPlayer(string playerName, string status, int rank, Alliance alliance, LqSfcDataContext dc)
        {
            if (playerName.IsNullOrEmpty())
                return null;
            Debug.Assert(playerName.StartsWith(" ") == false && playerName.EndsWith(" ") == false);
            Debug.Assert(status.StartsWith("(") == false);
            if (_players.ContainsKey(playerName))
                return _players[playerName];
            var player = dc.Players.Where(p => p.Name == playerName).SingleOrDefault();
            if (player != null)
            {
                player.Status = status;
                player.Rank = rank;
                player.Alliance = alliance;
                player.Updated = DateTime.UtcNow;
                //update player
                return player;
            }
            player= new Player()
            {
                Added = DateTime.UtcNow,
                Alliance = alliance,
                Name = playerName,
                Rank = rank,
                Status = status,
                Updated = DateTime.UtcNow
            };
            AppendXml(playerName, "newPlayer", false);
            dc.Players.InsertOnSubmit(player);
            dc.SubmitChanges();
            AppendXml(playerName, "playerInserted", true);
            _players.Add(playerName, player);
            return player;
        }

        private Alliance ProcessAlliance(string allianceShortName, LqSfcDataContext dc)
        {
            if (allianceShortName.IsNullOrEmpty())
                return null;
            Debug.Assert(allianceShortName.StartsWith(" ") == false && allianceShortName.EndsWith(" ") == false);
            if (_alliances.ContainsKey(allianceShortName))
                return _alliances[allianceShortName];
            var alliance = dc.Alliances.Where(a => a.Shortname == allianceShortName).SingleOrDefault();
            if (alliance != null)
                return alliance;

            alliance= new Alliance() { Shortname = allianceShortName, Added = DateTime.UtcNow, Updated = DateTime.UtcNow };
            dc.Alliances.InsertOnSubmit(alliance);
            _alliances.Add(allianceShortName, alliance);
            dc.SubmitChanges();
            AppendXml(allianceShortName, "allianceInserted", true);
            return alliance;
        }

        private static string GetSystemPrettyString(byte galaxy, int system)
        {
            return String.Format("[{0}:{1}]", galaxy, system);
        }
        private void ParseColony(string trimText)
        {

            var colony = ParseLine(trimText, AppendXml,ref _lastPosition);
            _colonies.Add(colony);

        }

        private static string  GetPlayerPattern()
        {
            return playerCapture + @"\s*" + statusCapture + @"?\s*#\s*" + rankCapture + @"\s*,\s*" +statusCapture+@"?\s*,\s*"+ allianceCapture;
        }
        private static string  GetLocationPattern()
        {
            return galaxyCapture + @"\s*:\s*" + systemCapture + @"\s*:\s*" + positionCapture;
        }

        string AppendXml(string text, string wrapperTag, bool newline)
        {
            try
            {
                using (var outFile = new StreamWriter(_logPath, true))
                {
                    var output = "<" + wrapperTag + ">" + text.Trim() + "</" + wrapperTag + ">";
                    if (newline)
                        outFile.WriteLine(output);
                    else
                        outFile.Write(output);
                }
            }
            catch (Exception)
            {
                
              
            }
           
            return text;
        }


    }
}
